Celem projektu była analiza bazy danych materiałów wykorzystywanych w tworzeniu baterii. Zbiór danych pochodzi z Materials Project - inicjatywy naukowej Departamentu Energii USA, której celem jest dostarczanie otwartych danych i narzędzi do analizy materiałów.
W raporcie przedstawiono strukturę analizowanego zbioru, dokonano analizy rozkładów wartości poszczególnych zmiennych wraz z identyfikacją cech dominujących oraz wartości odstających. Zinterpretowano macierz korelacji, która wykazała silne zależności między cechami opisującymi podobne właściwości fizyczne. Stworzono model predykcyjny na podstawie algorytmu Random Forest, który osiągnął wysoką dokładność predykcji wartości w zbiorze testowym zmiennej opisującej energię wolumetryczną.
W projekcie zostały wykorzystane następujące biblioteki:
- knitr,
- tidyverse,
- kableExtra,
- dplyr,
- tidyr,
- ggplot2,
- RColorBrewer,
- corrplot,
- plotly,
- caret.
library(knitr)
library(tidyverse)
library(kableExtra)
library(dplyr)
library(tidyr)
library(ggplot2)
library(RColorBrewer)
library(corrplot)
library(plotly)
library(caret)
Poniżej przedstawiono pierwsze 10 wierszy zbioru danych:
battery_data <- read.csv("mp_batteries.csv", fileEncoding = "UTF-8")
kable(head(battery_data, 10))
| Battery.ID | Battery.Formula | Working.Ion | Formula.Charge | Formula.Discharge | Max.Delta.Volume | Average.Voltage | Gravimetric.Capacity | Volumetric.Capacity | Gravimetric.Energy | Volumetric.Energy | Atomic.Fraction.Charge | Atomic.Fraction.Discharge | Stability.Charge | Stability.Discharge | Steps | Max.Voltage.Step |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| mp-30_Al | Al0-2Cu | Al | Cu | Al2Cu | 3.0433992 | 0.0890331 | 1368.48055 | 5562.7901 | 121.840086 | 495.272533 | 0.0000000 | 0.6666667 | 0.0000000 | 0.0000000 | 1 | 0 |
| mp-1022721_Al | Al1-3Cu | Al | AlCu | Al3Cu | 1.2436528 | -0.0215863 | 1112.93655 | 4418.9798 | -24.024232 | -95.389622 | 0.5000000 | 0.7500000 | 0.0740612 | 0.0962458 | 1 | 0 |
| mp-8637_Al | Al0-5Mo | Al | Mo | Al5Mo | 4.7625743 | 0.1227568 | 1741.50416 | 7175.7017 | 213.781556 | 880.866507 | 0.0000000 | 0.8333333 | 0.4114601 | 0.0452120 | 1 | 0 |
| mp-129_Al | Al0-12Mo | Al | Mo | Al12Mo | 12.7238931 | 0.0431214 | 2298.81076 | 7346.2323 | 99.128013 | 316.780060 | 0.0000000 | 0.9230769 | 0.0000000 | 0.0114456 | 1 | 0 |
| mp-91_Al | Al0-12W | Al | W | Al12W | 12.4945977 | 0.0292342 | 1900.74513 | 7332.7186 | 55.566774 | 214.366205 | 0.0000000 | 0.9230769 | 0.0000000 | 0.0000000 | 1 | 0 |
| mp-1055908_Al | Al0-12Mn | Al | Mn | MnAl12 | 18.2361563 | 0.0397314 | 2547.69280 | 7592.9161 | 101.223298 | 301.676876 | 0.0000000 | 0.9230769 | 0.1454643 | 0.0000000 | 1 | 0 |
| mp-2658_Al | Al0-1Fe | Al | Fe | AlFe | 0.7711539 | 0.4717287 | 970.75702 | 5622.3562 | 457.933974 | 2652.226958 | 0.0000000 | 0.5000000 | 0.7613994 | 0.0000000 | 1 | 0 |
| mp-16722_Al | Al1-10.25V | Al | Al10V | Al41V4 | 0.0027108 | -0.0155827 | 61.37701 | 176.4151 | -0.956421 | -2.749028 | 0.9090909 | 0.9111111 | 0.0118097 | 0.0125861 | 1 | 0 |
| mp-998981_Al | Al1-3Ti | Al | TiAl | TiAl3 | 0.9562924 | 0.1602450 | 1248.40362 | 4248.4211 | 200.050419 | 680.788169 | 0.5000000 | 0.7500000 | 0.1415912 | 0.0244962 | 1 | 0 |
| mp-8633_K | K0-3Cr | K | Cr | K3Cr | 15.8029363 | -0.7487069 | 474.94813 | 667.5593 | -355.596958 | -499.806269 | 0.0000000 | 0.7500000 | 0.4025263 | 0.6621618 | 1 | 0 |
Zbiór danych zawiera informacje o składzie chemicznym i parametrach
wydajnościowych baterii, składa się z 4351 unikalnych rekordów opisanych
17 atrybutami:
- Battery ID: Identyfikator baterii,
- Battery Formula: Wzór chemiczny materiału baterii,
- Working Ion: Główny jon, który odpowiada za transport ładunku w
baterii,
- Formula Charge: Wzór chemiczny materiału baterii w stanie
naładowanym,
- Formula Discharge: Wzór chemiczny materiału baterii w stanie
rozładowanym,
- Max Delta Volume: Zmiana objętości w % dla danego kroku napięcia za
pomocą wzoru: max(charge, discharge)/min(charge, discharge) -1,
- Average Voltage: Średnie napięcie dla poszczególnego kroku
napięcia,
- Gravimetric Capacity: Pojemność grawimetryczna, czyli ilość energii na
jednostkę masy (mAh/g),
- Volumetric Capacity: Pojemność wolumetryczna, czyli ilość energii na
jednostkę objętości (mAh/cm³),
- Gravimetric Energy: Gęstość energii w odniesieniu do masy baterii
(Wh/kg),
- Volumetric Energy: Gęstość energii w odniesieniu do objętości baterii
(Wh/L),
- Atomic Fraction Charge: Udział atomowy składników w stanie
naładowanym,
- Atomic Fraction Discharge: Udział atomowy składników w stanie
rozładowanym,
- Stability Charge: Wskaźnik stabilności materiału w stanie
naładowanym,
- Stability Discharge: Wskaźnik stabilności materiału w stanie
rozładowanym,
- Steps: Liczba odrębnych kroków napięcia od pełnego naładowania do
rozładowania, oparta na stabilnych stanach pośrednich,
- Max Voltage Step: Maksymalna bezwzględna różnica między sąsiednimi
krokami napięcia.
missing_values <- sapply(battery_data, function(x) sum(is.na(x)))
missing_values <- data.frame(
Column = names(missing_values),
"Number of Missing Values" = as.numeric(missing_values)
)
if (sum(missing_values$"Number of Missing Values") == 0){
cat("W zbiorze nie ma brakujących wartości")
} else {
cat("Brakujace wartości: ")
kable(missing_values)
battery_data <- battery_data[rowSums(is.na(battery_data)) == 0, ]
}
## W zbiorze nie ma brakujących wartości
duplicates <- battery_data[duplicated(battery_data$ColumnName), ]
if (nrow(duplicates) == 0){
cat("#### W zbiorze nie ma duplikatów")
} else {
cat("#### Duplikaty: ")
duplicates
}
## #### W zbiorze nie ma duplikatów
Poniżej przedstawiono typ danych oraz liczbę unikalnych wartości każdego atrybutu, a także przedziały wartości atrybutów numerycznych.
dataset_size <- dim(battery_data)
names(dataset_size) <- c("Rows", "Columns")
dataset_size <- data.frame(
Dimension = names(dataset_size),
Count = as.numeric(dataset_size)
)
kable(dataset_size)%>%
kable_styling(full_width = F)
| Dimension | Count |
|---|---|
| Rows | 4351 |
| Columns | 17 |
distinct_values <- sapply(battery_data, function(x) n_distinct(x))
column_types <- sapply(battery_data, class)
data_summary <- data.frame(
"Data Type" = column_types,
"Distinct values" = distinct_values
)
kable(data_summary)
| Data.Type | Distinct.values | |
|---|---|---|
| Battery.ID | character | 4351 |
| Battery.Formula | character | 3301 |
| Working.Ion | character | 10 |
| Formula.Charge | character | 2096 |
| Formula.Discharge | character | 3173 |
| Max.Delta.Volume | numeric | 4342 |
| Average.Voltage | numeric | 4351 |
| Gravimetric.Capacity | numeric | 3330 |
| Volumetric.Capacity | numeric | 4342 |
| Gravimetric.Energy | numeric | 4351 |
| Volumetric.Energy | numeric | 4351 |
| Atomic.Fraction.Charge | numeric | 126 |
| Atomic.Fraction.Discharge | numeric | 192 |
| Stability.Charge | numeric | 3050 |
| Stability.Discharge | numeric | 3933 |
| Steps | integer | 6 |
| Max.Voltage.Step | numeric | 600 |
numeric_cols <- battery_data[sapply(battery_data, is.numeric)]
numeric_vars <- names(battery_data)[sapply(battery_data, is.numeric)]
kable(summary(numeric_cols))
| Max.Delta.Volume | Average.Voltage | Gravimetric.Capacity | Volumetric.Capacity | Gravimetric.Energy | Volumetric.Energy | Atomic.Fraction.Charge | Atomic.Fraction.Discharge | Stability.Charge | Stability.Discharge | Steps | Max.Voltage.Step | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Min. : 0.00002 | Min. :-7.755 | Min. : 5.176 | Min. : 24.08 | Min. :-583.5 | Min. :-2208.1 | Min. :0.00000 | Min. :0.007407 | Min. :0.00000 | Min. :0.00000 | Min. :1.000 | Min. : 0.0000 | |
| 1st Qu.: 0.01747 | 1st Qu.: 2.226 | 1st Qu.: 88.108 | 1st Qu.: 311.62 | 1st Qu.: 211.7 | 1st Qu.: 821.6 | 1st Qu.:0.00000 | 1st Qu.:0.086957 | 1st Qu.:0.03301 | 1st Qu.:0.01952 | 1st Qu.:1.000 | 1st Qu.: 0.0000 | |
| Median : 0.04203 | Median : 3.301 | Median : 130.691 | Median : 507.03 | Median : 401.8 | Median : 1463.8 | Median :0.00000 | Median :0.142857 | Median :0.07319 | Median :0.04878 | Median :1.000 | Median : 0.0000 | |
| Mean : 0.37531 | Mean : 3.083 | Mean : 158.291 | Mean : 610.62 | Mean : 444.1 | Mean : 1664.0 | Mean :0.03986 | Mean :0.159077 | Mean :0.14257 | Mean :0.12207 | Mean :1.167 | Mean : 0.1503 | |
| 3rd Qu.: 0.08595 | 3rd Qu.: 4.019 | 3rd Qu.: 187.600 | 3rd Qu.: 722.75 | 3rd Qu.: 614.4 | 3rd Qu.: 2252.3 | 3rd Qu.:0.04762 | 3rd Qu.:0.200000 | 3rd Qu.:0.13160 | 3rd Qu.:0.09299 | 3rd Qu.:1.000 | 3rd Qu.: 0.0000 | |
| Max. :293.19322 | Max. :54.569 | Max. :2557.627 | Max. :7619.19 | Max. :5926.9 | Max. :18305.9 | Max. :0.90909 | Max. :0.993333 | Max. :6.48710 | Max. :6.27781 | Max. :6.000 | Max. :26.9607 |
Poniżej przedstawiono rozkład wartości zmiennych w zbiorze danych.
Dla zmiennej kategorycznej Working Ion (opisującej główny jon baterii) wykorzystano wykres słupkowy, który przedstawia liczbę wystąpień poszczególnych pierwiastków. Szerokość przedziałów każdego słupka została obliczona za pomocą formuły Freedmana-Diaconisa: \[ \text{bin_width} = 2 \cdot \text{IQR(column)} \div \text{length(column)}^{1/3} \] Wyraźnie dominującą wartością zmiennej jest Li (głównym jonem baterii jest lit), liczba baterii o tym głównym jonie stanowi 56% całego zbioru danych. Wapń (Ca), magnez (Mg), sód (Na) oraz cynk (Zn) występują po 300-500 razy, natomiast najrzadziej w zbiorze pojawia się cez (Cs - 33 wystąpienia) oraz rubid (Rb - 50 wystąpień).
palette <- c("#21130d", "#df7d32", "#4f322a", "#e28755", "#873e23", "#7e6e54", "#fdbd53", "#8b1a00", "#ee7a41", "#cbb677")
ggplotly(ggplot(battery_data, aes(x = Working.Ion, fill = Working.Ion)) +
geom_bar() +
scale_fill_manual(values = palette) +
theme_minimal() +
labs(
title = "Value Distribution of Working Ion",
x = "Working Ion",
y = "Count"
),
tooltip = c("x", "y")
)
Rozkład zmiennych numerycznych widoczny jest na poniższym wykresie. W prawym górnym rogu możliwy jest wybór z listy zmiennej do wyświetlenia.
bw <- sapply(numeric_cols, function(column) {
2 * IQR(column, na.rm = TRUE) / length(column)^(1/3)
})
nbinsx_values <- sapply(names(bw), function(col_name) {
range <- max(numeric_cols[[col_name]], na.rm = TRUE) - min(numeric_cols[[col_name]], na.rm = TRUE)
ceiling(range / bw[[col_name]])
})
plot <- plot_ly(
data = battery_data,
x = ~get(numeric_vars[1]),
type = 'histogram',
marker = list(color = '#df7d32',
line = list(
color = 'black',
width = 1
),
nbinsx = nbinsx_values[[numeric_vars[1]]])
) %>%
layout(
title = paste("Value Distribution of", numeric_vars[1]),
xaxis = list(title = numeric_vars[1]),
yaxis = list(title = "Count"),
updatemenus = list(
list(
buttons = lapply(seq_along(numeric_vars), function(i) {
list(
method = "update",
args = list(
list(x = list(battery_data[[numeric_vars[i]]])),
list(title = list(text = paste("Value Distribution of", numeric_vars[i])),
xaxis = list(title = numeric_vars[i]))
),
label = numeric_vars[i]
)
}),
direction = "down",
x = 1,
y = 1,
showactive = TRUE
)
)
) %>%
config(displayModeBar = TRUE)
plot <- plot %>%
layout(
hoverinfo = 'x+y',
showlegend = FALSE
)
plot
Dla zmiany objętości zdecydowana większość wartości (95%) znajduje się w przedziale (-0.25 - 0.25), pozostałe przedziały są widoczne na wykresie dopiero po przybliżeniu ze względu na małą liczbę wystąpień.
Większość wartości średniego napięcia znajduje się w przedziałach (3.2 - 3.4) oraz (3.8 - 4.0). Wykres przedstawiający rozkład posiada długi ogon z prawej strony ze względu na dwie wartości odstające (43.57 oraz 54.57).
Dominują niskie wartości pojemności grawimetrycznej. Widoczny jest peak w przedziale 100 - 120 mAh/g, gdzie liczba wystąpień wynosi 570, co wskazuje na stosunkowo niską pojemność grawimetryczną większości materiałów. Około 80% osi x to bardzo niskie słupki reprezentujące przedziały po 1-2 wystąpienia per przedział.
Wykres jest silnie asymetryczny prawostronnie. Główna część obserwacji znajduje się w przedziale do 1200 mAh/cm³. Najwyższy słupek histogramu reprezentuje przedział (550 - 600 mAh/cm³), nieco niższe są słupki z przedziałami od 150 do 400 mAh/cm³.
Znacząca liczba wartości znajduje się między 0 a 1200 Wh/kg, z najwyższymi słupkami reprezentującymi przedziały (350 - 400 wystąpień) oraz (400 - 459 wystąpień). Wykres jest prawostronnie skośny ze względu na wartości odstające w okolicach 4500 i 5900 Wh/kg.
Wartości energii wolumetrycznej przyjmują głównie wartości od 0 do 5000. Prawy ogon histogramu jest bardziej wydłużony od lewego. Słupek o największej liczbie 184 obserwacji reprezentuje przedział 1200 - 1300 Wh/L. Ponadto pojawiają się wartości odstajace równe nawet ponad 18000 Wh/L.
Wykres jest silnie skośny, 74% wartości znajduje się w przedziale (-0.005 - 0.005), następnie widoczny jest duży spadek do 1 wystąpienia per przedział. Pozostałe wartości są bardziej rozproszone. W dalszej części wykresu liczba wystąpień lekko rośnie do wartości około 0.14 na osi x, po czym ponownie spada.
Wykres jest asymetryczny, większość danych skupia się w niższych wartościach z kilkoma wyjątkami w okolicach wartości 0.5 i 0.75. Udział atomowy składników w stanie rozładowanym jest zróżnicowany, ale z tendencją do niskich wartości, na co wskazuje dominujący przedział (0.14 - 0.15).
Najwięcej obserwacji znajduje się blisko wartości 0, dominujący słupek reprezentuje przedział (0.025-0.075). Pozostałe wartości są rozłożone bardziej równomiernie w dalszej części wykresu. Występuje tu silna skośność prawostronna.
Dla stanu rozładowanego większość danych skoncentrowana jest w niskich wartościach, podobnie jak w przypadku wskaźnika stabilności materiału w stanie naładowanym. Ponad 1/3 obserwacji zawiera się w przedziale (0.025 - 0.075). Wartości większe od 1 pojawiają się rzadko, maksymalnie 4 na przedział, liczba wystąpień maleje wraz ze wzrostem wskaźnika stabilności.
Dominującą wartością zmiennej Steps jest 1, które stanowi 86% baterii z analizowanego zbioru. Z każdą kolejną wartością liczba obserwacji maleje kilkukrotnie. Wartość 2 występuje 7 razy mniej niż wartość 1, natomiast wartość 6 występuje w zbiorze tylko raz.
74% wartości znajduje się w przedziale od -0.005 do -0.00499. Reszta obserwacji (z wyjątkiem outlierów w okolicach wartości 8.5 oraz 27) przyjmuje wartości do 4, co wskazuje na to, że bezwzględne różnice między sąsiednimi krokami napięcia sa niewielkie.
Macierz korelacji pokazuje zależności między atrybutami numerycznymi. Wartości na przecięciach kolumn i wierszy oznaczają korelację między tymi dwoma cechami i przyjmują wartości w przedziale od -1 do 1, gdzie wartości z przedziału (0,1] oznaczają dodatnią korelację, 0 - całkowity brak korelacji, a w przedziale [-1;0) - ujemną korelację.
correlation_matrix <- battery_data %>%
select(numeric_vars) %>%
cor
corrplot(correlation_matrix,
method = "color",
type = "upper",
addCoef.col = "black",
tl.col = "black",
tl.srt = 45,
cl.lim = c(-1, 1),
diag = FALSE,
number.cex = 0.55,
mar = c(0, 0, 1, 0),
col = colorRampPalette(c("#803c14", "beige", "darkgreen"))(200)
)
Można zauważyć, że istnieją silne zależności między zmiennymi opisującymi podobne zjawiska fizyczne. Najsilniejsza dodatnia korelacja (0.93) jest między Gravimetric Energy oraz Volumetric Energy, które opisują gęstość energii w odniesieniu do odpowiednio masy i objętości baterii.
Podobnie wygląda to w przypadku, również silnej, korelacji między Gravimetric.Capacity a Volumetric.Capacity wynoszącej 0.86. Obie zmienne odnoszą się do ilości energii: na jednostkę masy (pojemność grawimetryczna) i na jednostkę objętości (pojemność wolumetryczna).
Niewiele słabsza (0.80) jest zależność między Stability.Charge i Stability.Discharge, co wskazuje na to, że stabilność materiału w stanie naładowanym i rozładowanym są mocno powiązane. Poniżej przedstawiono wykresy obrazujące opisane korelacje.
energy <- ggplot(battery_data, aes(x = Gravimetric.Energy, y = Volumetric.Energy)) +
geom_point(color = "darkgreen", alpha = 0.6) +
geom_smooth(method = "lm", color = "black", se = FALSE) +
labs(
title = "Correlation between gravimetric energy and volumetric energy (0.93)",
x = "Gravimetric.Energy",
y = "Volumetric.Energy"
) +
theme_minimal()
capacity <- ggplot(battery_data, aes(x = Gravimetric.Capacity, y = Volumetric.Capacity)) +
geom_point(color = "darkgreen", alpha = 0.6) +
geom_smooth(method = "lm", color = "black", se = FALSE) +
labs(
title = "Correlation between gravimetric capacity and volumetric capacity (0.86)",
x = "Gravimetric.Capacity",
y = "Volumetric.Capacity"
) +
theme_minimal()
stability <- ggplot(battery_data, aes(x = Stability.Charge, y = Stability.Discharge)) +
geom_point(color = "darkgreen", alpha = 0.6) +
geom_smooth(method = "lm", color = "black", se = FALSE) +
labs(
title = "Correlation between stability of the material when charged and discharged (0.80)",
x = "Stability.Charge",
y = "Stability.Discharge"
) +
theme_minimal()
print(energy)
print(capacity)
print(stability)
W celu analizy danych w zbiorze przeprowadzono predykcję zmiennej Volumetric.Energy. Dane podzielono na zbiór treningowy (72.5% danych) i zbiór testowy (27.5%). Do trenowania modelu wykorzystano algorytm Random Forest z 10 drzewami decyzyjnymi, natomiast do oceny modelu użyto 10-krotnej walidacji krzyżowej.
Model osiągnął najlepsze wyniki dla liczby predyktorów uwzględnianych przy każdym podziale w drzewach (mtry) równej 11. Optymalny model został wybrany na podstawie najniższej wartości RMSE (Root Mean Square Error — pierwiastek z błędu średniokwadratowego) równej ok. 210.47. Ponadto współczynnik determinacji \(R^2\) wyniósł 0.968, co wskazuje, że model wyjaśnia 96,8% zmienności zmiennej celu. Średni błąd bezwzględny (MAE) dodatkowo potwierdził wysoką dokładność predykcji.
numeric_cols = battery_data[, numeric_vars]
training_data <- createDataPartition(numeric_cols$Volumetric.Energy, p = 0.725, list = FALSE)
train <- numeric_cols[training_data, ]
test <- numeric_cols[-training_data, ]
ctrl <- trainControl(
method = "cv",
number = 10)
model <- train(Volumetric.Energy ~ .,
data = train,
method = "rf",
trControl = ctrl,
ntree = 10)
model
## Random Forest
##
## 3156 samples
## 11 predictor
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 2840, 2841, 2840, 2840, 2840, 2841, ...
## Resampling results across tuning parameters:
##
## mtry RMSE Rsquared MAE
## 2 355.5293 0.9192336 170.56523
## 6 251.9360 0.9564503 105.18338
## 11 210.4664 0.9683720 87.69412
##
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was mtry = 11.
ggplot(model) + theme_bw()
Aby sprawdzić, jak dobrze model wypada na nowych danych, dokonano oceny jego wydajności na zbiorze testowym. Model bardzo dobrze dopasował się do danych osiągając wysoką jakość predykcji na zbiorze testowym. Pomimo, że wartości miar RMSE (238.86) oraz MAE (90.33) były nieco wyższe, a współczynnika determinacji (\(R^2\) = 0.9716) niższa w porównaniu do wyników walidacji (co oznacza nieco gorsze dopasowanie), są to niewielkie różnice. Model wciąż zapewnia trafne predykcje, co potwierdza między innymi wysoka wartość współczynnika determinacji. Oznacza to, że model wyjaśnia 97.16% zmienności w danych testowych. Otrzymane wyniki sugerują, że model charakteryzuje się zarówno wysoką dokładnością, jak i zdolnością do generalizacji.
predictions <- predict(model, newdata = test)
metrics <- postResample(predictions, test$Volumetric.Energy)
metrics
## RMSE Rsquared MAE
## 238.8642686 0.9716024 90.3265948